package internal

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"runtime"
	"strings"
	"time"

	"bmember/pkg/rpc"

	"bmember/pkg/db/pgex"

	"github.com/go-playground/validator/v10"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/labstack/echo/v4"
	echo_middleware "github.com/labstack/echo/v4/middleware"
	"google.golang.org/grpc"

	"bmember/internal/conf"
	"bmember/internal/dao"
	mq_handler "bmember/internal/interface/mq"
	"bmember/internal/interface/rest"
	rpc_handler "bmember/internal/interface/rpc"
	"bmember/internal/service"
	"bmember/pkg/db/redisex"
	"bmember/pkg/log"
	// "bmember/pkg/mail"
	"bmember/pkg/middleware"
	"bmember/pkg/mq"
	"gitee.com/quantum-force/of-utils/pkg/mail"
)

type (
	server struct {
		cfg             *conf.Config
		logger          log.Factory
		echo            *echo.Echo
		grpc            *grpc.Server
		redis           *redisex.RedisEX
		pgsql           *pgex.SqlEx
		nsqConsumer     *mq.NsqConsumer
		payClientPool   *rpc.Pool
		adminClientPool *rpc.Pool
		openClientPool  *rpc.Pool
		smsClientPool   *rpc.Pool
	}
	// customValidator 自定义 validator
	customValidator struct {
		validator *validator.Validate
	}
)

// NewServer new server
func NewServer(logger log.Factory, cfg *conf.Config) *server {
	return &server{
		cfg:    cfg,
		logger: logger,
	}
}

// Validate validate
func (cv *customValidator) Validate(i interface{}) error {
	return cv.validator.Struct(i)
}

func defaultRouters(e *echo.Echo) {
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "少侠，你骨骼惊奇！")
	})
	e.GET("/ping", func(c echo.Context) error {
		return c.String(http.StatusOK, "pong")
	})
	e.HEAD("/ping", func(c echo.Context) error {
		return c.String(http.StatusOK, "pong")
	})
}

func (s *server) initRestServer() {
	e := echo.New()
	s.echo = e
	e.Debug = s.cfg.Base.Debug
	e.Validator = &customValidator{validator: validator.New()}
	e.Use(echo_middleware.CORSWithConfig(echo_middleware.CORSConfig{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
		AllowHeaders: []string{"Content-Type", "Authorization", "G-Token", "B-Token"},
	}))
	e.Use(middleware.EchoTracer, middleware.EchoRequestID, middleware.EchoStandardLogger, middleware.EchoErrorHandler, middleware.EchoRecover)
	e.HTTPErrorHandler = middleware.UnCatchErrorHandler
	// default router
	defaultRouters(e)
	// base hander
	rest.InitRestBase(s.logger, s.cfg)
	// member handler
	mRouter := e.Group("/member/api")
	mHandler := rest.NewMemberHandler()
	mHandler.Register(mRouter)
	// card handler
	cHandler := e.Group("/member/api/card")
	gHandler := rest.NewCardHandler()
	gHandler.Register(cHandler)
	// order handler
	oRouter := e.Group("member/api/asset")
	oHandler := rest.NewAssetHandler()
	oHandler.Register(oRouter)
	// schedule handler
	sRouter := e.Group("member/api/schedule")
	sHandler := rest.NewScheduleHandler()
	sHandler.Register(sRouter)
	// http server
	go func() {
		s.logger.Bg().Info(fmt.Sprintf("Http Server start at: %s", s.cfg.Http.Address))
		e.HideBanner = true
		err := e.Start(s.cfg.Http.Address)
		if err != nil {
			s.logger.Bg().Fatal(fmt.Sprintf("Http Server Failed: %s", err.Error()))
		}
	}()
}

func (s *server) initRpcServer() {
	s.grpc = grpc.NewServer(
		grpc.UnaryInterceptor(
			grpc_middleware.ChainUnaryServer(
				middleware.GrpcUnaryRequestIDInterceptor(),
				middleware.GrpcUnaryTracerInterceptor(),
				middleware.GrpcUnaryStdLoggerInterceptor(),
				middleware.GrpcUnaryRecoveryInterceptor(),
				middleware.GrpcUnaryValidatorInterceptor(),
				middleware.GrpcUnaryErrorInterceptor())))
	// rpc handler 初始化
	rpc_handler.InitRpcBase(s.logger, s.cfg)
	// order handler 初始化
	oHandler := rpc_handler.NewAssetRpcHandler()
	oHandler.RegisterHandler(s.grpc)
	// member handler 初始化
	mHandler := rpc_handler.NewMemberRpcHandler()
	mHandler.RegisterHandler(s.grpc)

	// grpc server
	go func() {
		// init grpc
		lis, err := net.Listen("tcp", s.cfg.Rpc.Port)
		if err != nil {
			s.logger.Bg().Panic(fmt.Sprintf("Grpc Server Failed: %v", err))
		}
		s.logger.Bg().Info(fmt.Sprintf("Grpc Server Start at: %s", s.cfg.Rpc.Port))
		if err = s.grpc.Serve(lis); err != nil {
			s.logger.Bg().Panic(fmt.Sprintf("Grpc Server Failed: %v", err))
		}
	}()
}

func (s *server) initMqConsumer() {
	s.nsqConsumer = mq.NewNsqConsumer(s.cfg.Mq.NSQLookupds, 10*time.Second, 2)
	s.nsqConsumer.Use(middleware.MqTracer, middleware.MqRequestId, middleware.MqRecovery)
	// mq handler
	mq_handler.InitMqBase(s.logger, s.cfg)
	mHandler := mq_handler.NewMqHandler()
	mHandler.Register(s.nsqConsumer)
}

// Close 关闭
func (s *server) Close() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if s.echo != nil {
		_ = s.echo.Shutdown(ctx)
	}
	if s.grpc != nil {
		s.grpc.GracefulStop()
	}
	if s.pgsql != nil {
		_ = s.pgsql.Close()
	}
	if s.redis != nil {
		_ = s.redis.Close()
	}
	if s.nsqConsumer != nil {
		s.nsqConsumer.Close()
	}
	if s.payClientPool != nil {
		s.payClientPool.Close()
	}
	if s.openClientPool != nil {
		s.openClientPool.Close()
	}
	if s.adminClientPool != nil {
		s.adminClientPool.Close()
	}
	if s.smsClientPool != nil {
		s.adminClientPool.Close()
	}
}

func (s *server) initBilinRpcPools() {
	// 比邻支付
	clientPool, err := rpc.New(func() (*grpc.ClientConn, error) {
		return rpc.NewGrpcConn(s.cfg.Rpc.PayRPC, s.logger)
	}, 3, 10*time.Minute, 30*time.Minute)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("bilin pay rpc server init failed %s", err.Error()))
	}
	s.payClientPool = clientPool
	// GetAdminClient admin 后台 rpc client
	clientPool, err = rpc.New(func() (*grpc.ClientConn, error) {
		return rpc.NewGrpcConn(s.cfg.Rpc.AdminRPC, s.logger)
	}, 3, 10*time.Minute, 30*time.Minute)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("bilin admin rpc server init failed %s", err.Error()))
	}
	s.adminClientPool = clientPool

	// GetOpenClient 授权平台 rpc client
	clientPool, err = rpc.New(func() (*grpc.ClientConn, error) {
		return rpc.NewGrpcConn(s.cfg.Rpc.OpenRPC, s.logger)
	}, 3, 10*time.Minute, 30*time.Minute)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("bilin open rpc server init failed %s", err.Error()))
	}
	s.openClientPool = clientPool

	// GetGSMSClient 获取短信 sms 客户端
	clientPool, err = rpc.New(func() (*grpc.ClientConn, error) {
		return rpc.NewGrpcConn(s.cfg.Rpc.GSmsRPC, s.logger)
	}, 3, 10*time.Minute, 30*time.Minute)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("bilin gsms rpc server init failed %s", err.Error()))
	}
	s.smsClientPool = clientPool

}

// dao 层初始化
func (s *server) initDao() {
	// mysql
	var err error
	dbCfg := s.cfg.Pgsql
	s.pgsql, err = pgex.NewSqlEx(dbCfg.Host, dbCfg.Port, dbCfg.User, dbCfg.Password, dbCfg.Dbname, dbCfg.MaxIdleConns, dbCfg.MaxOpenConns, dbCfg.IdleTimeout)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("postgresql init failed: %s", err.Error()))
	}
	// redis
	cCfg := s.cfg.Redis
	s.redis, err = redisex.NewRedisEX(cCfg.Address, cCfg.Password, cCfg.DefaultDB, cCfg.MaxRetries, cCfg.IdleTimeout)
	if err != nil {
		s.logger.Bg().Panic(fmt.Sprintf("redis init failed: %s", err.Error()))
	}
	// init rpc client pools
	s.initBilinRpcPools()
	// nsq
	nsq := mq.NewNsqProducer(s.cfg.Mq.NSQDAddress)

	// dao 基类
	dao.InitDao(s.cfg, s.pgsql, s.redis, nsq)
	// group dao
	dao.InitCardDao()
	// member dao
	dao.InitMemberDao()
	// member account
	dao.InitMemberAccountDao()
	// login channel dao
	dao.InitLoginChannelDao()
	// member address
	dao.InitMemberAddressDao()
	// orders dao
	dao.InitOrderDao(s.payClientPool)
	// admin dao
	dao.InitBilinVendorDao(s.adminClientPool, s.smsClientPool, s.openClientPool)
	// integral log dao
	dao.InitIntegralLogDao()
	// message dao
	dao.InitMessageDao()
	// balance log dao
	dao.InitBalanceDao()
	// schedule
	dao.InitScheduleDao()
}

// service 层初始化
func (s *server) initService() {
	// service 基类
	service.InitService(s.logger, s.cfg)
	// group service
	service.InitCardSrv()
	// member service
	service.InitMemberSrv()
	// asset service
	service.InitAssetSrv()
	// schedule service
	service.InitScheduleSrv()
}

// InitComponents 组件初始化
func (s *server) InitComponents() {
	// dao layer
	s.initDao()
	// service layer
	s.initService()
}

// 初始通知邮件
func (s *server) initBugNotify() {
	// 邮件
	if s.cfg.Base.BugAccount != "" {
		cfg := &mail.Config{
			Addr:     fmt.Sprintf("%s:%s", s.cfg.Mail.Host, s.cfg.Mail.Port),
			Name:     s.cfg.Base.BugAccountName,
			From:     s.cfg.Base.BugAccount,
			Password: s.cfg.Base.BugAccountPwd,
			StartSSL: true,
		}
		_ = mail.Init(cfg)
		_, file, line, _ := runtime.Caller(1)
		_ = mail.AsyncSend(&mail.Message{
			Subject: "这是一封来自bilin-member的测试bug报告是否可用的邮件",
			Content: strings.NewReader(fmt.Sprintf("请忽略.(Mehtod:initBugNotify,File:%s,Line:%d)", file, line)),
			To:      s.cfg.Base.BugReportTos,
		}, func(err error) {
			if err != nil {
				s.logger.Bg().Error(fmt.Sprintf("邮件发送错误: %v", err))
			}
		})
	}
}

// Run 运行
func (s *server) Run() {
	// 组件初始化
	s.InitComponents()
	// sever middleware 准备
	middleware.InitLogger(s.logger)
	middleware.InitRedis(s.redis)
	// rest 服务初始化
	s.initRestServer()
	// rpc 服务初始化
	s.initRpcServer()
	// nsq 服务初始化
	s.initMqConsumer()
	// bug邮件初始化
	s.initBugNotify()
}
